package org.infodavid.common.impl.services.validators;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

import javax.persistence.PersistenceException;

import org.hibernate.validator.ClassValidator;
import org.hibernate.validator.InvalidValue;

import org.infodavid.common.impl.services.helpers.ValidatorHelper;
import org.infodavid.common.services.IApplicationContext;
import org.infodavid.common.services.exceptions.ServiceException;
import org.infodavid.common.services.validators.IValidator;

/**
 * Abstract class for all Validator class
 * @param <T>
 */
public abstract class AbstractValidator<T extends Serializable> implements IValidator<T> {

	/** The Constant ERROR_VALIDATE. */
	private static final String ERROR_VALIDATE = "[%s]: %s;";

	/**
	 * Class Value
	 */
	private Class<T> classValue = getTypeParameterClass();

	/*
	 * (non-Javadoc)
	 * @see org.infodavid.common.services.validators.IValidator#validate(org.infodavid.common.services.
	 * IApplicationContext, java.io.Serializable)
	 */
	public void validate(final IApplicationContext context, final T value) throws ServiceException,
	    PersistenceException {
		if (context == null) {
			throw new IllegalArgumentException("context");
		}

		if (value == null) {
			throw new IllegalArgumentException(classValue.getSimpleName());
		}

		// Set context to access database when checking unique constraints
		ValidatorHelper.updateApplicationContext(context);

		// Use Hibernate Validator to validate object
		ClassValidator<T> dataValidator = new ClassValidator<T>(classValue);

		InvalidValue[] invalidValues = dataValidator.getInvalidValues(value);

		// write error and throw to caller
		if (invalidValues.length > 0) {
			StringBuilder buffer = new StringBuilder(invalidValues.length);

			for (InvalidValue invalidValue : invalidValues) {
				if ((invalidValue.getPropertyPath() != null)
				    && (invalidValue.getPropertyPath().length() > 0)) {
					buffer.append(String.format(ERROR_VALIDATE, invalidValue.getPropertyPath(),
					    invalidValue.getMessage()));
				}
				else
				{
					buffer.append(invalidValue.getMessage() + ";");
				}
			}

			// remove Semi-colon
			if (buffer.toString().length() > 0) {
				throw new IllegalArgumentException(buffer.toString().substring(0, buffer.toString().length() - 1));
			}
		}
	}

	/**
	 * @return Generic Type Name
	 */
	public String getGenericType() {
		return classValue.getSimpleName();
	}

	/**
	 * Get generic type of actual class
	 * @return
	 */
	@SuppressWarnings("unchecked")
	private Class<T> getTypeParameterClass() {
		Type type = getClass().getGenericSuperclass();
		Class<T> result = null;

		if (type instanceof ParameterizedType) {
			ParameterizedType paramType = (ParameterizedType)type;

			if (paramType.getActualTypeArguments().length > 0) {
				result = (Class<T>)paramType.getActualTypeArguments()[0];
			}
		}

		return result;
	}
}
